﻿using Ganss.Xss;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Qy.Web.Authorize;
class XssFilterUtility
{
    private readonly HtmlSanitizer sanitizer;
    public XssFilterUtility()
    {
        sanitizer = new HtmlSanitizer();
        //sanitizer.AllowedTags.Add("div");//标签白名单
        sanitizer.AllowedAttributes.Add("class");//标签属性白名单,默认没有class标签属性
        //sanitizer.AllowedCssProperties.Add("font-family");//CSS属性白名单
    }
    /// <summary>
    /// XSS过滤
    /// </summary>
    /// <param name="html">html代码</param>
    /// <returns>过滤结果</returns>
    public string Filter(string html)
    {
        string str = sanitizer.Sanitize(html);
        return str;
    }
}

public static class ModelStateDictionaryExtensions
{
    /// <summary>
    /// 获取ModelStateDictionary中包含的所有错误信息
    /// </summary>
    /// <param name="modelStateDictionary"></param>
    /// <returns></returns>
    public static string GetErrorMessages(this ModelStateDictionary modelStateDictionary)
    {
        string messages = "";
        List<string> Keys = modelStateDictionary.Keys.ToList();
        //获取每一个key对应的ModelStateDictionary
        foreach (var key in Keys)
        {
            var errors = modelStateDictionary[key].Errors.ToList();
            //将错误描述添加到sb中
            foreach (var error in errors)
            {
                messages += error.ErrorMessage + "; ";
            }
        }
        return messages;
    }
}

public class ModelStateFilter : ActionFilterAttribute
{
    private readonly XssFilterUtility xss;
    public ModelStateFilter()
    {
        xss = new XssFilterUtility();
    }
    /// <summary>
    /// 执行Action前校验
    /// </summary>
    /// <param name="context"></param>
    /// <param name="next"></param>
    /// <returns></returns>
    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        //获取Action参数集合
        var ps = context.ActionDescriptor.Parameters;
        //遍历参数集合
        foreach (var p in ps)
        {
            //if (context.ActionArguments[p.Name] != null)
            //{
            //当参数等于字符串
            if (p.ParameterType.Equals(typeof(string)))
            {
                context.ActionArguments[p.Name] = xss.Filter(context.ActionArguments[p.Name].ToString());
            }
            else if (p.ParameterType.IsClass)//当参数等于类
            {
                //XSS过滤
                ModelXssFieldFilter(p.Name, p.ParameterType, context.ActionArguments[p.Name]);
            }
            // }
        }

        //类型验证
        if (!context.ModelState.IsValid)
        {
            JsonResult result = new(new { status = 0, msg = context.ModelState.GetErrorMessages() })
            {
                ContentType = "application/json",
            };
            context.Result = result;
        }
        else
            await base.OnActionExecutionAsync(context, next);
    }

    /// <summary>
    /// 遍历修改类的字符串属性
    /// </summary>
    /// <param name="key">类名</param>
    /// <param name="t">数据类型</param>
    /// <param name="obj">对象</param>
    /// <returns></returns>
    private object ModelXssFieldFilter(string key, Type t, object obj)
    {
        //获取类的属性集合
        // var ats = t.GetCustomAttributes(typeof(XssFieldFilterAttribute), false);
        if (obj != null)
        {
            //获取类的属性集合
            var pps = t.GetProperties();

            foreach (var pp in pps)
            {
                if (pp.GetValue(obj) != null)
                {
                    //当属性等于字符串
                    if (pp.PropertyType.Equals(typeof(string)))
                    {
                        string value = pp.GetValue(obj).ToString();
                        pp.SetValue(obj, xss.Filter(value));
                    }
                    else if (pp.PropertyType.IsClass)//当属性等于类进行递归
                    {
                        pp.SetValue(obj, ModelXssFieldFilter(pp.Name, pp.PropertyType, pp.GetValue(obj)));
                    }
                }
            }
        }
        return obj;
    }
}



